// Copyright 2016 The kingshard Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

package backend

import (
	"fmt"
	"math/rand"
	"strconv"
	"strings"
	"time"

	"gitee.com/he3db/he3proxy/core/errors"
	"gitee.com/he3db/he3proxy/core/golog"
)

func Gcd(ary []int) int {
	var i int
	min := ary[0]
	length := len(ary)
	for i = 0; i < length; i++ {
		if ary[i] < min {
			min = ary[i]
		}
	}

	for {
		isCommon := true
		for i = 0; i < length; i++ {
			if ary[i]%min != 0 {
				isCommon = false
				break
			}
		}
		if isCommon {
			break
		}
		min--
		if min < 1 {
			break
		}
	}
	return min
}

func (n *Node) InitBalancer() {
	var sum int
	n.LastSlaveIndex = 0
	gcd := Gcd(n.SlaveWeights)

	for _, weight := range n.SlaveWeights {
		sum += weight / gcd
	}

	n.RoundRobinQ = make([]int, 0, sum)
	for index, weight := range n.SlaveWeights {
		for j := 0; j < weight/gcd; j++ {
			n.RoundRobinQ = append(n.RoundRobinQ, index)
		}
	}

	//random order
	if 1 < len(n.SlaveWeights) {
		r := rand.New(rand.NewSource(time.Now().UnixNano()))
		for i := 0; i < sum; i++ {
			x := r.Intn(sum)
			temp := n.RoundRobinQ[x]
			other := sum % (x + 1)
			n.RoundRobinQ[x] = n.RoundRobinQ[other]
			n.RoundRobinQ[other] = temp
		}
	}
}

func (n *Node) GetNextSlave() (*DB, error) {
	if n.Cfg.LoadBalanceMode == "weight" {
		var index int
		queueLen := len(n.RoundRobinQ)
		if queueLen == 0 {
			return nil, errors.ErrNoDatabase
		}
		if queueLen == 1 {
			index = n.RoundRobinQ[0]
			return n.Slave[index], nil
		}

		n.LastSlaveIndex = n.LastSlaveIndex % queueLen
		index = n.RoundRobinQ[n.LastSlaveIndex]
		if len(n.Slave) <= index {
			return nil, errors.ErrNoDatabase
		}
		db := n.Slave[index]
		n.LastSlaveIndex++
		n.LastSlaveIndex = n.LastSlaveIndex % queueLen
		return db, nil
	} else if n.Cfg.LoadBalanceMode == "lsn" {
		// Determine whether the LSN number meets the requirements
		newestLsn := n.NodeLsn[strings.Split(n.Master.addr, ":")[0]]
		// store master node lsn (Decimal LSN)
		var masterLsn int64
		var err error
		// Storage standby node index where LSN meet the conditions
		lsnSlice := make([]int, 0, 0)
		golog.Debug("balancer", "GetNextSlave", "master node lsn: "+newestLsn, 0)
		if masterLsn, err = strconv.ParseInt(strings.ReplaceAll(newestLsn, "/", ""), 16, 64); err != nil {
			golog.Error("balancer", "GetNextSlave", "master node parse 16hex to 10hex err : "+err.Error(), 0)
		} else {
			golog.Debug("balancer", "GetNextSlave",
				fmt.Sprintf("master node addr: [%s], 10hex lsn: [%d]", n.Master.addr, masterLsn), 0)
			for i, db := range n.Slave {
				lsn16 := n.NodeLsn[strings.Split(db.addr, ":")[0]]
				golog.Debug("balancer", "GetNextSlave",
					fmt.Sprintf("slave node index: [%d], addr: [%s], 16hex lsn: [%s]", i, db.addr, lsn16), 0)
				if parseUint, err := strconv.ParseInt(strings.ReplaceAll(lsn16, "/", ""), 16, 64); err != nil {
					golog.Error("balancer", "GetNextSlave", "parse 16hex to 10hex err : "+err.Error(), 0)
				} else {
					golog.Debug("balancer", "GetNextSlave",
						fmt.Sprintf("slave node index: [%d], addr: [%s], 10hex lsn: [%d]", i, db.addr, parseUint), 0)
					if parseUint >= masterLsn {
						lsnSlice = append(lsnSlice, i)
					}
				}
			}
		}
		// Stores the subscript of the last selected node
		var bestNodeIndex int
		// Record the maximum load score of the node
		maxLoadScore := 0
		// If the LSN of no standby node meets the conditions,
		// an error will be reported and the request will fall to the master node.
		if len(lsnSlice) <= 0 {
			return nil, errors.ErrFormat("no slave node fix the lsn")
		} else if len(lsnSlice) == 1 {
			// If there is only one node, select this node without judging the load score
			bestNodeIndex = lsnSlice[0]
		} else {
			// Compare the optimal load of the LSN qualified nodes in a round robin manner
			// and serve as the node requesting the load
			for _, index := range lsnSlice {
				if n.NodeLoad[index] >= maxLoadScore {
					bestNodeIndex = index
					maxLoadScore = n.NodeLoad[index]
				}
			}
		}

		// According to the LSN condition, the index satisfying the node is extracted,
		// and then the appropriate node is selected in the judgment.
		// TODO LSN --> Cold and hot data --> node load, will impl 'Cold and hot data' in the future
		// TODO Risk point: double network card problem.
		//  The IP of internal communication in the cluster is inconsistent with the IP of external user connection.
		//  The problem may be caused by IP mismatch when taking LSN.
		return n.Slave[bestNodeIndex], nil
	} else if n.Cfg.LoadBalanceMode == "metric" {
		var index int
		if n.BestNodeIndexByMetric == -1 {
			index = 0
		} else {
			index = n.BestNodeIndexByMetric
		}
		return n.Slave[index], nil
	} else {
		return nil, errors.ErrFormat("load balance mode not support: %s", n.Cfg.LoadBalanceMode)
	}
}
